package atlantik.game;

import static atlantik.Log.debug;

import java.awt.Component;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.net.Socket;

import javax.net.SocketFactory;
import javax.xml.parsers.ParserConfigurationException;

import modelz.Updateable;
import modelz.ui.Peer;

import org.xml.sax.SAXException;

import atlantik.Log;

public class AtlantikClient implements Runnable, Atlantik {

	private final AtlantikState state;
	private final AtlantikEventDispatcher dispatcher;
	private final Socket socket;
	private final BufferedReader in;
	private final PrintWriter out;
	private Thread thread;

	public AtlantikClient(String hostname, int port, AtlantikListener listener) throws IOException,  ParserConfigurationException {
		state = new AtlantikState(this);
		dispatcher = new AtlantikEventDispatcher(state, listener);

		SocketFactory factory = SocketFactory.getDefault();
		InetAddress[] addrs = InetAddress.getAllByName(hostname);
		for( InetAddress addr : addrs )
			debug("Connecting to " + addr.toString());
		socket = factory.createSocket(addrs[0], port);
		in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
		out = new PrintWriter( new OutputStreamWriter( socket.getOutputStream() ) );
	}

	public boolean isConnected() {
		return thread != null;
	}
	
	public void connect() {
		(thread = new Thread(this)).start();
	}

	public void disconnect() {
		thread = null;
	}

	public AtlantikState getState() {
		return state;
	}

	public void setName(String name) {
		write(".n%s", name);
	}

	public void rollDice()
	{
		write(".r");
	}

	public void buyEstate()
	{
		write(".eb");
	}

	public void auctionEstate()
	{
		write(".ea");
	}

	public void startGame()
	{
		write(".gs");
	}

	public void reconnect(String cookie)
	{
		write(".R%s", cookie);
		write(".f");
	}

	public void leaveGame()
	{
		write(".gx");
	}

	public void endTurn()
	{
		write(".E");
	}

	public void tokenConfirmation(Estate estate)
	{
		if( estate == null )
			return;
		write(".t%d", estate.getId());
	}

	public void estateToggleMortgage(Estate estate)
	{
		if( estate == null )
			return;
		write(".em%d", estate.getId());
	}

	public void estateHouseBuy(Estate estate)
	{
		if( estate == null )
			return;
		write(".hb%d", estate.getId());
	}

	public void estateHouseSell(Estate estate)
	{
		if( estate == null )
			return;
		write(".hs%d", estate.getId());
	}

	public void newGame(String gameType)
	{
		write(".gn%s", gameType);
	}

	public void join(Game game)
	{
		write(".gj%d", game.getId());
	}

	public void chat(String msg)
	{
		write(msg);
	}

	public void command(String command)
	{
		write(command);
	}

	public void newTrade(Player player)
	{
		if( player == null )
			return;
		write(".Tn%d", player.getId());
	}

	public void kickPlayer(Player player)
	{
		if( player == null )
			return;
		write(".gk%d", player.getId());
	}

	public void tradeUpdateEstate(Trade trade, Estate estate, Player player)
	{
		if( trade == null || estate == null || player == null )
			return;
		write(".Te%d:%d:%d", trade.getId(), estate.getId(), player.getId());
	}

	public void tradeUpdateMoney(Trade trade, int money, Player pFrom, Player pTo)
	{
		if( trade == null || pFrom == null || pTo == null )
			return;
		write(".Tm%d:%d:%d:%d", trade.getId(), pFrom.getId(), pTo.getId(), money);
	}

	public void tradeReject(Trade trade)
	{
		if( trade == null )
			return;
		write(".Tr%d", trade.getId());
	}

	public void tradeAccept(Trade trade)
	{
		if( trade == null )
			return;
		write(".Ta%d:%d", trade.getId(), trade.getRevision());
	}

	public void auctionBid(Auction auction, int amount)
	{
		if( auction == null )
			return;
		write(".ab%d:%d", auction.getId(), amount);
	}

	public void setImage( String name)
	{
		write(".pi%s", name);
	}

	public void jailPay()
	{
		write(".jp");
	}

	public void jailRoll()
	{
		write(".jr");
	}

	public void jailCard()
	{
		write(".jc");
	}

	public void updateOption(Game.Option option)
	{
		write(String.format("%s%s", option.getId(), ((Game.Option)option).value));
	}

	public void write(String line, Object... args) {
			write(String.format(line, args));
	}
	public void write(String line) {
		if( out == null ) {
			System.err.println("Not connected");
			return;
		}
		Log.debug("->" + line);
		out.println(line);
		out.flush();
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void run() {
		while( Thread.currentThread() == thread ) {
			try {
				String line = in.readLine();
				try {
					dispatcher.dispatch(line);
				} catch(SAXException e) {
					resend();
				} catch(InstantiationException e) {
					throw new RuntimeException(e);
				}
			} catch( IOException e ) {
				System.out.println(e);
			}
		}
		try {
			socket.close();
		} catch( IOException e ) {}
	}


	void resend() {
		write(".f");
	}

	public <T> T createObject(Class<T> cls) throws Exception {
		Constructor<T> c = cls.getConstructor(Atlantik.class);
		return c.newInstance(this);
	}

	public <C extends Peer<T, ? extends Component>, T extends Updateable<T>> C createPeer(
			Class<C> cls, T item) throws Exception {
		Constructor<C> c = cls.getConstructor(Atlantik.class, item.getClass());
		return c.newInstance(this, item);
	}

}
